/*-------------------------------------------------------------------------
 *
 * tsquery_op.c
 *	  与 tsquery 的各种操作
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/tsquery_op.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "lib/qunique.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"

Datum tsquery_numnode(PG_FUNCTION_ARGS)
{
	TSQuery		fc_query = PG_GETARG_TSQUERY(0);
	int			fc_nnode = fc_query->size;

	PG_FREE_IF_COPY(fc_query, 0);
	PG_RETURN_INT32(fc_nnode);
}

static QTNode * fc_join_tsqueries(TSQuery fc_a, TSQuery fc_b, int8 fc_operator, uint16 fc_distance)
{
	QTNode	   *fc_res = (QTNode *) palloc0(sizeof(QTNode));

	fc_res->flags |= QTN_NEEDFREE;

	fc_res->valnode = (QueryItem *) palloc0(sizeof(QueryItem));
	fc_res->valnode->type = QI_OPR;
	fc_res->valnode->qoperator.oper = fc_operator;
	if (fc_operator == OP_PHRASE)
		fc_res->valnode->qoperator.distance = fc_distance;

	fc_res->child = (QTNode **) palloc0(sizeof(QTNode *) * 2);
	fc_res->child[0] = QT2QTN(GETQUERY(fc_b), GETOPERAND(fc_b));
	fc_res->child[1] = QT2QTN(GETQUERY(fc_a), GETOPERAND(fc_a));
	fc_res->nchild = 2;

	return fc_res;
}

Datum tsquery_and(PG_FUNCTION_ARGS)
{
	TSQuery		fc_a = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		fc_b = PG_GETARG_TSQUERY_COPY(1);
	QTNode	   *fc_res;
	TSQuery		fc_query;

	if (fc_a->size == 0)
	{
		PG_FREE_IF_COPY(fc_a, 1);
		PG_RETURN_POINTER(fc_b);
	}
	else if (fc_b->size == 0)
	{
		PG_FREE_IF_COPY(fc_b, 1);
		PG_RETURN_POINTER(fc_a);
	}

	fc_res = fc_join_tsqueries(fc_a, fc_b, OP_AND, 0);

	fc_query = QTN2QT(fc_res);

	QTNFree(fc_res);
	PG_FREE_IF_COPY(fc_a, 0);
	PG_FREE_IF_COPY(fc_b, 1);

	PG_RETURN_TSQUERY(fc_query);
}

Datum tsquery_or(PG_FUNCTION_ARGS)
{
	TSQuery		fc_a = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		fc_b = PG_GETARG_TSQUERY_COPY(1);
	QTNode	   *fc_res;
	TSQuery		fc_query;

	if (fc_a->size == 0)
	{
		PG_FREE_IF_COPY(fc_a, 1);
		PG_RETURN_POINTER(fc_b);
	}
	else if (fc_b->size == 0)
	{
		PG_FREE_IF_COPY(fc_b, 1);
		PG_RETURN_POINTER(fc_a);
	}

	fc_res = fc_join_tsqueries(fc_a, fc_b, OP_OR, 0);

	fc_query = QTN2QT(fc_res);

	QTNFree(fc_res);
	PG_FREE_IF_COPY(fc_a, 0);
	PG_FREE_IF_COPY(fc_b, 1);

	PG_RETURN_TSQUERY(fc_query);
}

Datum tsquery_phrase_distance(PG_FUNCTION_ARGS)
{
	TSQuery		fc_a = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		fc_b = PG_GETARG_TSQUERY_COPY(1);
	QTNode	   *fc_res;
	TSQuery		fc_query;
	int32		fc_distance = PG_GETARG_INT32(2);

	if (fc_distance < 0 || fc_distance > MAXENTRYPOS)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("distance in phrase operator must be an integer value between zero and %d inclusive",
						MAXENTRYPOS)));
	if (fc_a->size == 0)
	{
		PG_FREE_IF_COPY(fc_a, 1);
		PG_RETURN_POINTER(fc_b);
	}
	else if (fc_b->size == 0)
	{
		PG_FREE_IF_COPY(fc_b, 1);
		PG_RETURN_POINTER(fc_a);
	}

	fc_res = fc_join_tsqueries(fc_a, fc_b, OP_PHRASE, (uint16) fc_distance);

	fc_query = QTN2QT(fc_res);

	QTNFree(fc_res);
	PG_FREE_IF_COPY(fc_a, 0);
	PG_FREE_IF_COPY(fc_b, 1);

	PG_RETURN_TSQUERY(fc_query);
}

Datum tsquery_phrase(PG_FUNCTION_ARGS)
{
	PG_RETURN_POINTER(DirectFunctionCall3(tsquery_phrase_distance,
										  PG_GETARG_DATUM(0),
										  PG_GETARG_DATUM(1),
										  Int32GetDatum(1)));
}

Datum tsquery_not(PG_FUNCTION_ARGS)
{
	TSQuery		fc_a = PG_GETARG_TSQUERY_COPY(0);
	QTNode	   *fc_res;
	TSQuery		fc_query;

	if (fc_a->size == 0)
		PG_RETURN_POINTER(fc_a);

	fc_res = (QTNode *) palloc0(sizeof(QTNode));

	fc_res->flags |= QTN_NEEDFREE;

	fc_res->valnode = (QueryItem *) palloc0(sizeof(QueryItem));
	fc_res->valnode->type = QI_OPR;
	fc_res->valnode->qoperator.oper = OP_NOT;

	fc_res->child = (QTNode **) palloc0(sizeof(QTNode *));
	fc_res->child[0] = QT2QTN(GETQUERY(fc_a), GETOPERAND(fc_a));
	fc_res->nchild = 1;

	fc_query = QTN2QT(fc_res);

	QTNFree(fc_res);
	PG_FREE_IF_COPY(fc_a, 0);

	PG_RETURN_POINTER(fc_query);
}

static int fc_CompareTSQ(TSQuery fc_a, TSQuery fc_b)
{
	if (fc_a->size != fc_b->size)
	{
		return (fc_a->size < fc_b->size) ? -1 : 1;
	}
	else if (VARSIZE(fc_a) != VARSIZE(fc_b))
	{
		return (VARSIZE(fc_a) < VARSIZE(fc_b)) ? -1 : 1;
	}
	else if (fc_a->size != 0)
	{
		QTNode	   *fc_an = QT2QTN(GETQUERY(fc_a), GETOPERAND(fc_a));
		QTNode	   *fc_bn = QT2QTN(GETQUERY(fc_b), GETOPERAND(fc_b));
		int			fc_res = QTNodeCompare(fc_an, fc_bn);

		QTNFree(fc_an);
		QTNFree(fc_bn);

		return fc_res;
	}

	return 0;
}

Datum tsquery_cmp(PG_FUNCTION_ARGS)
{
	TSQuery		fc_a = PG_GETARG_TSQUERY_COPY(0);
	TSQuery		fc_b = PG_GETARG_TSQUERY_COPY(1);
	int			fc_res = fc_CompareTSQ(fc_a, fc_b);

	PG_FREE_IF_COPY(fc_a, 0);
	PG_FREE_IF_COPY(fc_b, 1);

	PG_RETURN_INT32(fc_res);
}

#define CMPFUNC( NAME, CONDITION )				\
Datum											\
NAME(PG_FUNCTION_ARGS) {						\
	TSQuery  fc_a = PG_GETARG_TSQUERY_COPY(0);		\
	TSQuery  fc_b = PG_GETARG_TSQUERY_COPY(1);		\
	int fc_res = fc_CompareTSQ(fc_a,fc_b);					\
												\
	PG_FREE_IF_COPY(fc_a,0);						\
	PG_FREE_IF_COPY(fc_b,1);						\
												\
	PG_RETURN_BOOL( CONDITION );				\
}	\
/* 保持编译器安静 - 不添加额外的 ; */			\
extern int no_such_variable

CMPFUNC(tsquery_lt, fc_res < 0);
CMPFUNC(tsquery_le, fc_res <= 0);
CMPFUNC(tsquery_eq, fc_res == 0);
CMPFUNC(tsquery_ge, fc_res >= 0);
CMPFUNC(tsquery_gt, fc_res > 0);
CMPFUNC(tsquery_ne, fc_res != 0);

TSQuerySign
makeTSQuerySign(TSQuery fc_a)
{
	int			fc_i;
	QueryItem  *fc_ptr = GETQUERY(fc_a);
	TSQuerySign fc_sign = 0;

	for (fc_i = 0; fc_i < fc_a->size; fc_i++)
	{
		if (fc_ptr->type == QI_VAL)
			fc_sign |= ((TSQuerySign) 1) << (((unsigned int) fc_ptr->qoperand.valcrc) % TSQS_SIGLEN);
		fc_ptr++;
	}

	return fc_sign;
}

static char ** fc_collectTSQueryValues(TSQuery fc_a, int *fc_nvalues_p)
{
	QueryItem  *fc_ptr = GETQUERY(fc_a);
	char	   *fc_operand = GETOPERAND(fc_a);
	char	  **fc_values;
	int			fc_nvalues = 0;
	int			fc_i;

	fc_values = (char **) palloc(sizeof(char *) * fc_a->size);

	for (fc_i = 0; fc_i < fc_a->size; fc_i++)
	{
		if (fc_ptr->type == QI_VAL)
		{
			int			fc_len = fc_ptr->qoperand.length;
			char	   *fc_val;

			fc_val = palloc(fc_len + 1);
			memcpy(fc_val, fc_operand + fc_ptr->qoperand.distance, fc_len);
			fc_val[fc_len] = '\0';

			fc_values[fc_nvalues++] = fc_val;
		}
		fc_ptr++;
	}

	*fc_nvalues_p = fc_nvalues;
	return fc_values;
}

static int fc_cmp_string(const void *fc_a, const void *fc_b)
{
	const char *fc_sa = *((char *const *) fc_a);
	const char *fc_sb = *((char *const *) fc_b);

	return strcmp(fc_sa, fc_sb);
}

Datum tsq_mcontains(PG_FUNCTION_ARGS)
{
	TSQuery		fc_query = PG_GETARG_TSQUERY(0);
	TSQuery		fc_ex = PG_GETARG_TSQUERY(1);
	char	  **fc_query_values;
	int			fc_query_nvalues;
	char	  **fc_ex_values;
	int			fc_ex_nvalues;
	bool		fc_result = true;

	/* 将查询术语提取到数组中 */
	fc_query_values = fc_collectTSQueryValues(fc_query, &fc_query_nvalues);
	fc_ex_values = fc_collectTSQueryValues(fc_ex, &fc_ex_nvalues);

	
	qsort(fc_query_values, fc_query_nvalues, sizeof(char *), fc_cmp_string);
	fc_query_nvalues = qunique(fc_query_values, fc_query_nvalues, sizeof(char *),
							fc_cmp_string);
	qsort(fc_ex_values, fc_ex_nvalues, sizeof(char *), fc_cmp_string);
	fc_ex_nvalues = qunique(fc_ex_values, fc_ex_nvalues, sizeof(char *), fc_cmp_string);

	if (fc_ex_nvalues > fc_query_nvalues)
		fc_result = false;
	else
	{
		int			fc_i;
		int			fc_j = 0;

		for (fc_i = 0; fc_i < fc_ex_nvalues; fc_i++)
		{
			for (; fc_j < fc_query_nvalues; fc_j++)
			{
				if (strcmp(fc_ex_values[fc_i], fc_query_values[fc_j]) == 0)
					break;
			}
			if (fc_j == fc_query_nvalues)
			{
				fc_result = false;
				break;
			}
		}
	}

	PG_RETURN_BOOL(fc_result);
}

Datum tsq_mcontained(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(tsq_mcontains,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)));
}
